﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CutAways.Modeling;

namespace CutAways.Geometry
{
	[Serializable]
    public struct Vector
    {
        private float x, y, z;

		public static readonly Vector XAxis = new Vector(1f, 0f, 0f);
		public static readonly Vector YAxis = new Vector(0f, 1f, 0f);
		public static readonly Vector ZAxis = new Vector(0f, 0f, 1f);

        public float X
        {
            get { return x; }
			set { x = value; }
        }

        public float Y
        {
            get { return y; }
			set { y = value; }
        }

        public float Z
        {
            get { return z; }
			set { z = value; }
        }
		
		public Vector(float x, float y, float z)
        {
            this.x = x;
            this.y = y;
            this.z = z;
        }

		public float GetLength()
		{
			return (float)Math.Sqrt(x * x + y * y + z * z);
		}

		public static Vector SetLength(Vector v, float newLength)
		{
			float factor = newLength / v.GetLength();
			return new Vector(v.x * factor, v.y * factor, v.z * factor);
		}
				
		public void Normalize()
		{
            float len = GetLength();
			x /= len;
			y /= len;
			z /= len;
		}
		
		public static Vector operator + (Vector v1, Vector v2)
		{
			return new Vector(v1.x+v2.x, v1.y+v2.y, v1.z+v2.z);
		}

        public static Vector operator -(Vector v1, Vector v2)
		{
			return new Vector(v1.x-v2.x, v1.y-v2.y, v1.z-v2.z);
		}

        public static Vector operator +(Vector v1)
		{
			return v1;
		}

        public static Vector operator -(Vector v1)
		{
			return v1 * (-1f);
		}

        public static Vector operator *(Vector v1, float f)
		{
			return new Vector(v1.x*f, v1.y*f, v1.z*f);
		}

        public static Vector operator /(Vector v1, float f)
		{
			return new Vector(v1.x/f, v1.y/f, v1.z/f);
		}

        public static Vector operator *(Vector v1, Vector v2)
        {
            return Cross(v1, v2);
        }
		
		public static float Dot(Vector v1, Vector v2)
		{
			return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z;
		}
		
		public static Vector Cross(Vector v1, Vector v2)
		{
			return new Vector(
				v1.y*v2.z - v1.z*v2.y,
				v1.z*v2.x - v1.x*v2.z,
				v1.x*v2.y - v1.y*v2.x);
		}

        public void Rotate(float angle, Vector axis)
        {
            float cosTheta = (float)Math.Cos(angle);
            float sinTheta = (float)Math.Sin(angle);

            Vector rotated = new Vector();
            rotated.x  = (cosTheta + (1 - cosTheta) * axis.x * axis.x) * x;
            rotated.x += ((1 - cosTheta) * axis.x * axis.y - axis.z * sinTheta) * y;
            rotated.x += ((1 - cosTheta) * axis.x * axis.z + axis.y * sinTheta) * z;

            rotated.y = ((1 - cosTheta) * axis.x * axis.y + axis.z * sinTheta) * x;
            rotated.y += (cosTheta + (1 - cosTheta) * axis.y * axis.y) * y;
            rotated.y += ((1 - cosTheta) * axis.y * axis.z - axis.x * sinTheta) * z;

            rotated.z = ((1 - cosTheta) * axis.x * axis.z - axis.y * sinTheta) * x;
            rotated.z += ((1 - cosTheta) * axis.y * axis.z + axis.x * sinTheta) * y;
            rotated.z += (cosTheta + (1 - cosTheta) * axis.z * axis.z) * z;

            this.x = rotated.X;
			this.y = rotated.Y;
			this.z = rotated.Z;
        }

		public static Vector Norm(Vector v)
		{
			v.Normalize();
			return v;
		}

		public static Vector Rotate(Vector v, float angle, Vector axis)
		{
			v.Rotate(angle, axis);
			return v;
		}

		public override string ToString()
		{
			return x + "," + y + "," + z;
		}

		public static Vector CenterOfLink(Vector a, Vector b)
		{
			return new Vector(a.x + (b.x - a.x) * 0.5f, a.y + (b.y - a.y) * 0.5f, a.z + (b.z - a.z) * 0.5f);
		}

		public static implicit operator Vertex(Vector v)
		{
			return new Vertex(v.x, v.y, v.z);
		}

		

		public static Vector RotateAllAxis(Vector v, Vector rotationAngles) // vector of degrees expected
		{
			v = Vector.Rotate(v, Vector.DegreesToRadians(rotationAngles.Z), Vector.ZAxis);
			v = Vector.Rotate(v, Vector.DegreesToRadians(rotationAngles.Y), Vector.YAxis);
			v = Vector.Rotate(v, Vector.DegreesToRadians(rotationAngles.X), Vector.XAxis);
			return v;
		}

		public static Vector RotateAllAxisReversed(Vector v, Vector rotationAngles) // vector of degrees expected
		{
			v = Vector.Rotate(v, Vector.DegreesToRadians(rotationAngles.X), Vector.XAxis);
			v = Vector.Rotate(v, Vector.DegreesToRadians(rotationAngles.Y), Vector.YAxis);
			v = Vector.Rotate(v, Vector.DegreesToRadians(rotationAngles.Z), Vector.ZAxis);
			return v;
		}

		/// <summary>
		/// vrati vzdalenost tohoto bodu od primky ab
		/// </summary>
		/// <param name="a">prvni bod primky</param>
		/// <param name="b">druhy bod primky</param>
		public float DistanceFromLine(Vector a, Vector b)
		{
			Vector u = b - a;
			float x = Cross(u, a - this).GetLength();
			float y = u.GetLength();
			return x / y;
		}

		public static bool operator ==(Vector a, Vector b)
		{
			return a.x == b.x && a.y == b.y && a.z == b.z;
		}

		public static bool operator !=(Vector a, Vector b)
		{
			return !(a == b);
		}

		public static float SQR(float x)
		{
			return x * x;
		}
		
		public static float DegreesToRadians(float deg)
		{
			return deg * 1.74532925e-2f;
		}

		public static float AngleBetween(Vector a, Vector b, Vector normal)
		{
			a.Normalize();
			b.Normalize();
			float result = (float)Math.Atan2(Vector.Dot(normal, Vector.Cross(a, b)), Vector.Dot(a, b));
			//if (result < 0f)
			//    result = (float)(2.0 * Math.PI) + result;
			return result;
		}

		/// <summary>
		/// prevede uhel z intervalu [-pi,pi] na interval [0,2pi)
		/// </summary>
		/// <param name="angle">uhel z intervalu [-pi,pi]</param>
		/// <returns>uhel z intervalu [0,2pi)</returns>
		public static float GetValidAngle(float angle)
		{
			if (angle < 0f)
				return 2f * (float)Math.PI + angle;
			if (angle >= (float)Math.PI * 2f)
				return angle - (float)Math.PI * 2f;
			return angle;
		}

		public Vector ProjectionTo(Vector v)
		{
			float projection = Dot(this, v) / v.GetLength();
			return v * projection;
		}

		public static Vector LinearTransitionBetween(Vector v1, Vector v2, float progress)
		{
			return new Vector(v1.x + (v2.x - v1.x) * progress, v1.y + (v2.y - v1.y) * progress, v1.z + (v2.z - v1.z) * progress);
		}

		public static Vector NonLinearTransitionBetween(Vector v1, Vector v2, float progress)
		{
			return new Vector(NonLinearTransitionBetween(v1.x, v2.x, progress), NonLinearTransitionBetween(v1.y, v2.y, progress), NonLinearTransitionBetween(v1.z, v2.z, progress));
		}

		public static float NonLinearTransitionBetween(float begin, float end, float progress)
		{
			float coeficient = 0f;
			if (progress < 0.5f)
				coeficient = Vector.SQR(progress * 2f) * 0.5f;
			else
				coeficient = 1f - Vector.SQR((1f - progress) * 2f) * 0.5f;
			return begin + (end - begin) * coeficient;
		}
    }
}
